Skip to content

ADFA-4954 feat(kolibri): domain layer for content seeding — ids, selection, plan - #313

Draft
luisguzman-adfa wants to merge 1 commit into
mainfrom
feat/ADFA-4954-kolibri-android-seeding
Draft

ADFA-4954 feat(kolibri): domain layer for content seeding — ids, selection, plan#313
luisguzman-adfa wants to merge 1 commit into
mainfrom
feat/ADFA-4954-kolibri-android-seeding

Conversation

@luisguzman-adfa

Copy link
Copy Markdown
Collaborator

First slice of the Android side of Kolibri content seeding. Contracts only, per "shared contracts land first": no android.*, no HTTP, unit-testable on a plain JVM, so the data and service layers can be built against it.

ChannelId normalises the dashed and uppercase spellings of a channel or node id and rejects a Studio token outright — a token passed as an id ends in a 404 from the downloader with no useful message, so the error says so explicitly. Same fail-closed boundary ModuleName draws for module names.

ChannelSelection separates "the whole channel" from "named subtrees", because Kolibri accepts node_ids: [] and finishes successfully having transferred zero bytes. ofSubtrees() refuses to produce an empty selection, including when every supplied id fails validation.

SeedPlan answers whether a selection fits before committing to a download. fitsIn() returns null for "cannot tell" rather than false: the catalog only carries whole-channel sizes, and reading an over-estimate as "does not fit" would block seeding a small subtree of a large channel.

52 JVM unit tests.

NOT COMPILED — the sandbox has no javac, so type errors would not have been caught. Needs gradlew :app:testDebugUnitTest locally before merge.

…ction, plan

First slice of the Android side. Contracts only, per the "shared contracts land
first" rule: no android.*, no HTTP, so it is unit-testable on a plain JVM and the
data/service layers can be built against it.

ChannelId — what counts as a usable channel or node identifier. Normalises the
dashed and uppercase spellings, and rejects a Studio channel token outright:
a token passed where an id is expected ends in a 404 from the downloader with no
useful message, so looksLikeToken() exists purely to make the error say "that is
a token, resolve it first". Same fail-closed boundary ModuleName draws for module
names — an id reaches a request the box acts on.

ChannelSelection — the whole channel, or named subtrees. Encodes the trap in
Kolibri's API where omitting node_ids means everything and sending an empty list
means nothing: the importer accepts node_ids: [] and finishes successfully having
transferred zero bytes. wholeChannel() and ofSubtrees() are separate factories and
ofSubtrees() refuses to produce an empty selection, including when every supplied
id fails validation. Immutable, ids normalised and deduplicated, order kept.

SeedPlan — what is queued and whether it fits. Answers the space question before
committing to a download, which Kolibri cannot: it subtracts a 250 MB cushion when
computing free space, so it can report zero with room left and only finds out
mid-transfer. fitsIn() returns null for "cannot tell" rather than false, because
the catalog only carries whole-channel sizes: reading an over-estimate as "does
not fit" would block seeding a small subtree of a large channel, which is the case
a phone needs most.

52 JVM unit tests.

Verified: all six files parse; the domain imports only java.util; every method the
tests call exists; JUnit 4 only, matching build.gradle.

NOT COMPILED. The sandbox has no javac — no JDK, Maven Central and the Adoptium
release assets are both outside the allowlist, and jdk4py ships a runtime without
jdk.compiler. Type errors would not have been caught. Needs a local
`gradlew :app:testDebugUnitTest` before merge.
@luisguzman-adfa
luisguzman-adfa marked this pull request as draft July 31, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant